-
Notifications
You must be signed in to change notification settings - Fork 819
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC Host yielding API #2219
RFC Host yielding API #2219
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I salute the amount of work to make all that working correctly! Thanks!
I'll need some times to review this PR carefully and to think about the feature design exactly :-). I reckon the feature is great, but it will take time to evaluate the design/the approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting PR, thanks for going through and doing it. I don't understand how it all works yet, I'll probably have to look into what async-wormhole is doing to figure that out.
It's nice to have a proof of concept for this. I'm not currently well-informed enough to know what trade-offs this approach makes and if it's the right set of trade-offs for wasmer, but it seems minimally invasive...
Though changes like this in a system as complex as a Wasm VM can have a lot of side effects that we'll need to think through as well. I'll try to follow up on this more later once I've done some more research
|
||
#[ cfg(feature="async") ] | ||
/// Set the thread-specific yielder | ||
fn set_yielder(&mut self, _: *const std::ffi::c_void) {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This probably needs to be unsafe
and have a # Safety
section describing how to correctly call and/or implement this function
#[ cfg(feature="async") ] | ||
let (host_env, metadata) = { | ||
let host_env_set_yielder_fn = |env_ptr, yielder_ptr| { | ||
let env: &mut Env = unsafe { std::mem::transmute(env_ptr) }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's nice to be super explicit about what exactly is being transmuted from what to what to make bugs and intent more obvious. I'd prefer all transmute
being transmute::<A,B>
with explicit types whenever possible.
lib/api/src/externals/function.rs
Outdated
#[ cfg(feature="async") ] | ||
fn build_export_function_metadata<Env>( | ||
env: Env, | ||
import_init_function_ptr: for<'a> fn( | ||
&'a mut Env, | ||
&'a crate::Instance, | ||
) -> Result<(), crate::HostEnvInitError>, | ||
host_env_set_yielder_fn: fn(*mut std::ffi::c_void, *const std::ffi::c_void) | ||
) -> (*mut std::ffi::c_void, ExportFunctionMetadata) | ||
where | ||
Env: Clone + Sized + 'static + Send + Sync, | ||
{ | ||
let import_init_function_ptr = Some(unsafe { | ||
std::mem::transmute::<fn(_, _) -> Result<(), _>, fn(_, _) -> Result<(), _>>( | ||
import_init_function_ptr, | ||
) | ||
}); | ||
let host_env_clone_fn: fn(*mut std::ffi::c_void) -> *mut std::ffi::c_void = |ptr| { | ||
let env_ref: &Env = unsafe { | ||
ptr.cast::<Env>() | ||
.as_ref() | ||
.expect("`ptr` to the environment is null when cloning it") | ||
}; | ||
Box::into_raw(Box::new(env_ref.clone())) as _ | ||
}; | ||
let host_env_drop_fn: fn(*mut std::ffi::c_void) = |ptr| { | ||
unsafe { Box::from_raw(ptr.cast::<Env>()) }; | ||
}; | ||
let env = Box::into_raw(Box::new(env)) as _; | ||
|
||
// # Safety | ||
// - All these functions work on all threads | ||
// - The host env is `Send`. | ||
let metadata = unsafe { | ||
ExportFunctionMetadata::new( | ||
env, | ||
import_init_function_ptr, | ||
host_env_clone_fn, | ||
host_env_set_yielder_fn, | ||
host_env_drop_fn, | ||
) | ||
}; | ||
|
||
(env, metadata) | ||
} | ||
|
||
#[ cfg(not(feature="async")) ] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer having the cfg
logic internal to the original function rather than having 2 copies just because it's so precise, dangerous, and error-prone. The reason this function exists in the first place is to try to avoid duplicating this kind of code.
#[ cfg(feature="async") ] | ||
let (host_env, metadata) = { | ||
let host_env_set_yielder_fn = |env_ptr, yielder_ptr| { | ||
let env: &mut Env = unsafe { std::mem::transmute(env_ptr) }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here
lib/api/src/externals/yielder.rs
Outdated
impl Yielder { | ||
/// Create a new instance from a raw pointer to a yielder | ||
pub fn new(inner: *const std::ffi::c_void) -> Self { | ||
Self{ inner } | ||
} | ||
|
||
/// Get the `AsyncYielder` | ||
pub fn get(&self) -> &mut AsyncYielder<Result<Box<[Val]>, RuntimeError>> { | ||
let yielder: &mut AsyncYielder<Result<Box<[Val]>, RuntimeError>>= unsafe { std::mem::transmute(self.inner) }; | ||
|
||
yielder | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Either new
needs to be unsafe
or get
needs to be unsafe (or possibly both)
lib/api/src/instance.rs
Outdated
let res = func.call_wasm(&wasm, params, &mut results); | ||
|
||
if let Err(e) = res { | ||
return Err(e); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
⛳
let res = func.call_wasm(&wasm, params, &mut results)?;
lib/api/src/module.rs
Outdated
let instance_handle = self.artifact.instantiate( | ||
self.store.tunables(), | ||
resolver, | ||
Box::new(()), | ||
)?; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like unrelated formatting changes, we use 1 behind latest stable (though I think we may not have updated to 1.50 in this repo yet), would be good to run that version of rustfmt on the code
lib/derive/src/parse.rs
Outdated
#[ cfg(feature="async") ] | ||
Yielder { | ||
identifier: Option<LitStr>, | ||
span: Span, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor nit (I haven't review all the derive macro changes yet) but we probably want to unconditionally parse this attribute and error if the feature isn't enabled or otherwise generate code that will provide a useful error message in that case. Don't worry about doing all that work now for this experimental PR, just writing this down for future reference.
Thanks for taking a look. I should also note that the commit history is kind of a mess right now and the pull request makes some unnecessary changes, mainly because I pulled these changes out of a larger branch. |
Some thoughts on the API design. I believe a more ergonomic API would be: Call async functionlet instance = Instance::new(&module, import_object);
// It can be EightMbStack or OneMbStack
// This call is optional (and it can be EightMbStack by default if not called)
store.set_stack(EightMbStack);
instance.call_async("my_func"); Function definitionWe can allow functions to be defined async, and detect them with the Rust type system (automatically wrapping them). async fn my_host_func(env: &MyEnv) {
my_async_func().await;
} I'm not super convinced of having the yielder in the Env, but I'm not super sure what are the alternatives. Thoughts? Apart from this, we will need to add tests and benchmark the calls (similarly as we do benchmarking of other function calls) |
I like these changes a lot. Will close this pull request for now and reopen once I have time to implement them. Thanks for all the feedback! |
Hi,
This is not intended to be merged into master yet, but I am curious about your thoughts on the approach. Essentially this pull request tries to fix/implement #1127 using the
async-wormhole
crate.After enabling the
async
feature and you can call a function like so:This then executes the function in the userspace thread. You can yield host calls by exposing the Yielder to your WasmerEnv.
And then in the function call you can access the Yielder (which is just a clonable wrapper around
async-wormhole
's API).